Skip to content

feat(slack): Slack-button approvals for Cedar HITL gates (#112)#316

Closed
flamingquaks wants to merge 1 commit into
aws-samples:mainfrom
flamingquaks:feat/112-slack-hitl-approvals
Closed

feat(slack): Slack-button approvals for Cedar HITL gates (#112)#316
flamingquaks wants to merge 1 commit into
aws-samples:mainfrom
flamingquaks:feat/112-slack-hitl-approvals

Conversation

@flamingquaks

Copy link
Copy Markdown

Summary

Implements #112: when a Cedar HITL approval gate fires on a Slack-origin task, the Slack thread now gets an :lock: Approval required Block Kit message with ✅ Approve / ❌ Deny buttons. Clicking records the same atomic decision the CLI's bgagent approve/deny would — the operator never leaves Slack.

Design

Shared decision core (cdk/src/handlers/shared/approval-decision.ts). The trust-critical approve/deny logic — per-user rate limit, the ownership-guarded PENDING-only TransactWriteItems across the approvals + task tables, and the approval_decision_recorded audit event — is extracted into processApprovalDecision, and both approve-task.ts / deny-task.ts are refactored onto it. The HTTP handlers' status mapping is unchanged (their existing tests pass untouched), and the Slack path can no longer drift from the CLI path.

Rendering + routing.

  • slack-blocks.ts renders approval_requested with tool name, input preview, reason, severity, and timeout. Buttons (with confirm dialogs) render only for low/medium severity; high severity renders a CLI command hint instead — per design §11.2 finding feat: add FargateAgentStack as alternative compute backend #4, high gates cannot be approved from Slack.
  • approval_requested / approval_stranded graduate from forward-compat: added to the fanout ROUTABLE_MILESTONES allowlist (the agent emits them as agent_milestone carriers) and the Slack dispatcher's NOTIFIABLE_EVENTS; the cross-file consistency test now enforces renderability for both.

Interactions handler trust chain (approve_action:{task_id}:{request_id} / deny_action:…):

  1. Slack signature verification (existing).
  2. Slack identity → platform user via SlackUserMappingTable (user-initiated linking only; unlinked/pending → ephemeral "link first").
  3. Server-side severity re-check from the approvals row — the missing buttons on high-severity messages are UX, not authorization; a forged/replayed action_id is refused here.
  4. The shared decision core, where the transaction's user_id = :caller condition rejects a linked-but-non-owning user with the same no-oracle "not found" collapse as the HTTP path (§7.1 finding chore(deps): bump defu from 6.1.4 to 6.1.6 in /docs #6).
  5. Ephemeral response_url feedback for every outcome (ok / rate-limited / not-found / no-longer-awaiting).

Slack decisions always record scope: this_call — blanket scoped approvals remain CLI-only.

CDK wiring. SlackIntegration gains an optional taskApprovalsTable prop; the interactions Lambda gets approvals read/write + events write grants and the table env vars; the agent stack passes the existing TaskApprovalsTableV2 through. Without the prop, approval clicks degrade to an ephemeral "not configured" message.

Docs. CEDAR_HITL_GATES §11.2 gains an implementation note (constraints enforced directly in the interactions Lambda + row conditions, rather than the originally-sketched sts:AssumeRole proxy which added no enforcement the conditions don't already provide); SLACK_SETUP_GUIDE, USER_GUIDE gate-flow, and ROADMAP updated; Starlight mirrors regenerated.

Tests

  • approval-decision.test.ts — 11 cases pinning the surface-agnostic invariants (rate-limit short-circuit, ownership/decided collapse to not_found, task-row → not_awaiting, audit never written on failure, audit failure never fails the decision).
  • slack-interactions-approvals.test.ts — 11 cases including the two critical ones: forged action_id on a high-severity gate is refused server-side, and a non-owner's click fails the ownership condition with no existence oracle.
  • Block Kit renderer tests (7) and construct wiring tests (2).
  • Full cdk suite: 112 suites / 2015 tests passed; eslint clean; CDK↔CLI type-sync check OK.

Closes #112

🤖 Generated with Claude Code

…#112)

Closes the HITL loop end-to-end: when a Cedar approval gate fires on a
Slack-origin task, the thread gets an 🔒 Approval required message
with ✅ Approve / ❌ Deny buttons. Clicking records the same atomic
decision the CLI's `bgagent approve`/`deny` would.

Shared decision core:
- New `shared/approval-decision.ts` extracts the trust-critical
  approve/deny logic (per-user rate limit, ownership-guarded
  PENDING-only TransactWriteItems across approvals+task tables,
  approval_decision_recorded audit) into `processApprovalDecision`.
- `approve-task.ts` / `deny-task.ts` refactored onto the core (HTTP
  status mapping unchanged — existing handler tests pass untouched).

Slack rendering + routing:
- `slack-blocks.ts` renders `approval_requested` (tool, input preview,
  reason, severity, timeout) with confirm-dialog buttons for low/medium
  severity; high severity renders a CLI hint instead (§11.2 finding aws-samples#4:
  high gates are CLI-only). `approval_stranded` renders as a warning.
- `approval_requested` / `approval_stranded` graduate from
  forward-compat: added to ROUTABLE_MILESTONES (agent emits them as
  agent_milestone carriers) and NOTIFIABLE_EVENTS; consistency test
  updated to enforce renderability.

Interactions handler:
- `approve_action:{task_id}:{request_id}` / `deny_action:…` clicks:
  Slack signature verified → SlackUserMappingTable identity mapping
  (user-initiated links only) → SERVER-SIDE severity re-check from the
  approvals row (buttons are UX, not authorization) → shared decision
  core (ownership enforced in the transaction) → ephemeral feedback.
- Slack decisions always record scope=this_call; blanket scopes stay
  CLI-only.

Wiring:
- SlackIntegration gains optional `taskApprovalsTable`; interactions Fn
  gets approvals read/write + events write + env vars; agent stack
  passes the table through.

Docs: CEDAR_HITL_GATES §11.2 implementation note (direct enforcement
instead of the sketched sts:AssumeRole proxy), SLACK_SETUP_GUIDE,
USER_GUIDE gate-flow, ROADMAP (shipped entry + polish row trimmed),
Starlight mirrors regenerated.

Tests: approval-decision core (11), slack-interactions approvals (11,
incl. forged-action_id high-severity refusal and non-owner no-oracle
collapse), Block Kit renderers (7), construct wiring (2). Full cdk
suite: 2015 passed.

Closes aws-samples#112

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@flamingquaks flamingquaks requested a review from a team as a code owner June 11, 2026 09:48
@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 95.95808% with 27 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (main@af33bc0). Learn more about missing BASE report.

Files with missing lines Patch % Lines
cdk/src/handlers/deny-task.ts 71.73% 13 Missing ⚠️
cdk/src/handlers/slack-interactions.ts 94.07% 8 Missing ⚠️
cdk/src/handlers/approve-task.ts 87.75% 6 Missing ⚠️
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.
Additional details and impacted files
@@           Coverage Diff           @@
##             main     #316   +/-   ##
=======================================
  Coverage        ?   86.72%           
=======================================
  Files           ?      186           
  Lines           ?    43804           
  Branches        ?     3947           
=======================================
  Hits            ?    37987           
  Misses          ?     5817           
  Partials        ?        0           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add Slack-button approvals for Cedar HITL approval gates

2 participants